﻿using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Linq;

namespace AstoriaPlus
{
	/// <summary>
	/// Base class for data services that expose a processing pipeline made of pluggable behaviors that run 
	/// in a deterministic order.
	/// </summary>
	/// <typeparam name="T">Type of the data source object exposed by the data service.</typeparam>
	public abstract class PipelineDataService<T> : DataService<T>
	{
		private IPipelineBehavior[] behaviors;
		private DataServiceOperationContext operationContext;

		/// <summary>
		/// Initializes a new instance of the <see cref="PipelineDataService{T}"/> class with the given behaviors.
		/// </summary>
		protected PipelineDataService(IEnumerable<IPipelineBehavior> behaviors)
			: this(behaviors.ToArray())
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="PipelineDataService{T}"/> class with the given behaviors.
		/// </summary>
		protected PipelineDataService(params IPipelineBehavior[] behaviors)
		{
			this.PipelineContext = new PipelineContext();
			this.InitializePipeline(behaviors);

			this.ProcessingPipeline.ProcessingRequest += new EventHandler<DataServiceProcessingPipelineEventArgs>(OnProcessingRequest);
			this.ProcessingPipeline.ProcessingChangeset += new EventHandler<EventArgs>(OnProcessingChangeset);
			this.ProcessingPipeline.ProcessedChangeset += new EventHandler<EventArgs>(OnProcessedChangeset);
			this.ProcessingPipeline.ProcessedRequest += new EventHandler<DataServiceProcessingPipelineEventArgs>(OnProcessedRequest);
		}

		/// <summary>
		/// Gets the pipeline context that can be used by the data service to keep transient 
		/// state that is also available to the entire set of configured <see cref="IPipelineBehavior"/>s.
		/// </summary>
		public IPipelineContext PipelineContext { get; private set; }

		/// <summary>
		/// Initializes the pipeline with the given set of behaviors, overriding any 
		/// existing ones that might have been provided via the constructor.
		/// </summary>
		protected void InitializePipeline(IEnumerable<IPipelineBehavior> behaviors)
		{
			this.behaviors = behaviors.ToArray();
		}

		void OnProcessingRequest(object sender, DataServiceProcessingPipelineEventArgs e)
		{
			this.operationContext = e.OperationContext;

			foreach (var behavior in behaviors)
			{
				behavior.ProcessingRequest(this.PipelineContext, e.OperationContext);
			}
		}

		void OnProcessingChangeset(object sender, EventArgs e)
		{
			foreach (var behavior in behaviors)
			{
				behavior.ProcessingChangeset(this.PipelineContext, this.operationContext);
			}
		}

		void OnProcessedChangeset(object sender, EventArgs e)
		{
			foreach (var behavior in behaviors.Reverse())
			{
				behavior.ProcessedChangeset(this.PipelineContext, this.operationContext);
			}
		}

		void OnProcessedRequest(object sender, DataServiceProcessingPipelineEventArgs e)
		{
			foreach (var behavior in behaviors.Reverse())
			{
				behavior.ProcessedRequest(this.PipelineContext, e.OperationContext);
			}
		}

		/// <summary>
		/// Handles the exception by executing the pipeline behaviors <see cref="IPipelineBehavior.HandleException"/> callback.
		/// </summary>
		protected override void HandleException(HandleExceptionArgs args)
		{
			base.HandleException(args);

			foreach (var behavior in behaviors)
			{
				behavior.HandleException(this.PipelineContext, args);
			}
		}

		/// <summary>
		/// Called when processing of a request (can be within a batch) starts, and invokes the <see cref="IPipelineBehavior.StartProcessRequest"/> callback.
		/// </summary>
		protected override void OnStartProcessingRequest(ProcessRequestArgs args)
		{
			base.OnStartProcessingRequest(args);

			foreach (var behavior in behaviors)
			{
				behavior.StartProcessingRequest(this.PipelineContext, args);
			}
		}
	}
}
